package com.sunday.common.http.apache.sentinel;

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.sunday.common.core.enums.HttpExpandStatusEnum;
import com.sunday.common.http.apache.sentinel.config.SentinelApacheHttpClientConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.config.NamedElementChain;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;

/**
 * SentinelApacheHttpClientBuilder类继承了HttpClientBuilder，使用Apache HttpClient库并与阿里巴巴的Sentinel库整合，用于限流和熔断器的实现。
 * 这个类有一个名为 config 的成员变量，是 SentinelApacheHttpClientConfig 类型的实例，用于存储配置信息。
 * 类中的两个构造函数都是用来创建这个类的新实例，一个是默认构造函数，一个是接收一个 SentinelApacheHttpClientConfig 类型变量的构造函数。
 * customizeExecChain 方法是重写了父类的方法，它在请求的执行链中添加了一个用于Sentinel限流和熔断的处理器。
 * 在这个处理器中，通过调用config的getExtractor方法提取HTTP请求的一些信息，并通过调用config的getPrefix方法获取前缀。然后，它试图获取一个Sentinel资源定位器的入口，如果请求被Sentinel限流或者触发熔断机制，它将抛出一个 BlockException 异常，并返回一个包含熔断器状态代码和异常信息的HTTP响应。如果在请求执行过程中发生其他错误，异常将被追踪，然后抛出。
 * 最后，无论请求是否成功，资源追踪器的入口都会调用exit方法来标记资源释放。
 * 整个类的功能就是创建一个定制的Apache HttpClient，这个HttpClient与Sentinel进行整合，能够处理Sentinel的限流和熔断，从而提高系统的可用性和稳定性。
 *
 * @author zsy
 * @see com.alibaba.csp.sentinel.adapter.apache.httpclient.SentinelApacheHttpClientBuilder 参考 因为其中http util包已过时
 * @since 2024/1/19
 */
@Slf4j
public class SentinelApacheHttpClientBuilder extends HttpClientBuilder {

    private static final String SENTINEL_EXEC_CHAIN_HANDLER_NAME = "SYNC_SENTINEL";

    private final SentinelApacheHttpClientConfig config;

    public SentinelApacheHttpClientBuilder() {
        this(new SentinelApacheHttpClientConfig());
    }

    public SentinelApacheHttpClientBuilder(SentinelApacheHttpClientConfig config) {
        assert config != null : "config cannot be null";
        this.config = config;
    }

    @Override
    protected void customizeExecChain(final NamedElementChain<ExecChainHandler> execChainDefinition) {
        execChainDefinition.addFirst((request, scope, chain) -> {
            Entry entry = null;
            try {
                String name = config.getExtractor().extractor(request);
                if (!StringUtil.isEmpty(config.getPrefix())) {
                    name = config.getPrefix() + name;
                }
                entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
                return chain.proceed(request, scope);
            } catch (BlockException e) {
                /**
                 * 其中 ExecChainHandler 与异步 AsyncExecChainHandler 存在本质上的区别，前者是同步的，后者是异步的。
                 * ExecChainHandler 是存在返回值的，并可以作用在请求的执行链上，而 AsyncExecChainHandler 则是没有返回值的，需要借助 AsyncExecCallback 进行响应的返回。
                 * @see org.apache.hc.client5.http.classic.ExecChainHandler
                 * @see org.apache.hc.client5.http.async.AsyncExecChainHandler
                 */
                return new BasicClassicHttpResponse(HttpExpandStatusEnum.CIRCUIT_BREAK.code, e.toString());
            } catch (Throwable t) {
                Tracer.traceEntry(t, entry);
                throw t;
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }, SENTINEL_EXEC_CHAIN_HANDLER_NAME);
    }

}
